/**!
 * native search system
 *
 */

use common;
use std::fs;
use std::path::PathBuf;
use std::sync::Arc;
use tantivy::directory::MmapDirectory;
use tantivy::schema::Field;
use tantivy::schema::*;
use tantivy::{doc, Index, ReloadPolicy};
use tantivy::{IndexReader, IndexWriter};
use tantivy_jieba;
use tokio::sync::Mutex;

pub type Result<T> = std::result::Result<T, common::Error>;

pub struct Nss {
    index: Index,
    writer: Arc<Mutex<IndexWriter>>,
    reader: IndexReader,
    docid: Field,
    author: Field,
    fname: Field,
    body: Field,
}

pub fn get_chs_textopts() -> TextOptions {
    let text_indexing = TextFieldIndexing::default()
        .set_tokenizer("jieba") // Set custom tokenizer
        .set_index_option(IndexRecordOption::WithFreqsAndPositions);
    TextOptions::default()
        .set_indexing_options(text_indexing)
        .set_stored()
}

impl Nss {
    pub fn new(
        mut base_path: PathBuf,
        index_name: &str,
        writer_memory: usize,
    ) -> anyhow::Result<Self> {
        base_path.push(index_name);
        if !base_path.exists() {
            fs::create_dir(&base_path)?;
        }
        let dir = MmapDirectory::open(base_path)?;

        let mut schema_builder = Schema::builder();
        let docid = schema_builder.add_text_field("docid", STRING | STORED);
        let author = schema_builder.add_text_field("author", get_chs_textopts().clone());
        let fname = schema_builder.add_text_field("fname", get_chs_textopts().clone());
        let body = schema_builder.add_text_field("body", get_chs_textopts().clone());

        let schema = schema_builder.build();
        let index = Index::open_or_create(dir, schema)?;

        let tokenizer = tantivy_jieba::JiebaTokenizer {};

        index.tokenizers().register("jieba", tokenizer);

        let index_writer = index.writer(writer_memory)?;
        // index_writer.set_merge_policy(merge_policy);
        let writer = Arc::new(Mutex::new(index_writer));
        let reader = index
            .reader_builder()
            .reload_policy(ReloadPolicy::OnCommit)
            .try_into()?;
        Ok(Self {
            index,
            reader,
            writer,
            docid,
            author,
            fname,
            body,
        })
    }

    pub fn get_tables(&self) -> (Field, Field, Field, Field) {
        (
            self.docid.clone(),
            self.author.clone(),
            self.fname.clone(),
            self.body.clone(),
        )
    }

    pub fn get_index(&self) -> &Index {
        &self.index
    }

    pub fn get_reader(&self) -> &IndexReader {
        &self.reader
    }

    pub fn get_writer(&self) -> &Arc<Mutex<IndexWriter>> {
        &self.writer
    }

    pub async fn add_document(&self, add_doc: &common::SearchDoc) -> Result<()> {
        //let index_schema = self.index.schema();

        let index_writer = self.get_writer().lock().await;

        // let index_writer = writer_lock.lock().await;
        //  .map_err(|e|common::Error::Err4Result(e.to_string()))?;
        let (docid, author, fname, body) = self.get_tables();

        index_writer.add_document(doc!(
        docid => add_doc.docid.as_str(),
        author => add_doc.author.as_str(),
        fname =>  add_doc.fname.as_str(),
        body =>  add_doc.content.as_str(),
        ))?;

        Ok(())
    }
}

#[test]
fn test_nss() {
    let root = PathBuf::from(r".");
    let nss = Nss::new(root, "abc", 3000000).unwrap();
}
